package com.p7.architect.store.shoppingcart.infrastructure.repositoryimpl.diff;

import com.p7.architect.store.shoppingcart.domain.shared.Aggregate;
import com.p7.architect.store.shoppingcart.domain.shared.ValueObject;
import com.p7.architect.store.shoppingcart.infrastructure.util.DiffUtils;
import com.p7.architect.store.shoppingcart.infrastructure.util.ReflectionUtils;
import com.p7.architect.store.shoppingcart.infrastructure.util.SnapshotUtils;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@Slf4j
public class SnapshotContext<T extends Aggregate<ID>, ID extends ValueObject> {

    private Class<? extends T> targetClass;

    private final Map<ID, T> aggregateMap = new HashMap<>();

    public SnapshotContext(Class<? extends T> targetClass) {
        this.targetClass = targetClass;
    }

    public void attach(T aggregate) {
        if (aggregate.getId() != null) {
            if (!aggregateMap.containsKey(aggregate.getId())) {
                this.merge(aggregate);
            }
        }
    }

    public void detach(T aggregate) {
        if (aggregate.getId() != null) {
            aggregateMap.remove(aggregate.getId());
        }
    }

    public EntityDiff detectChanges(T aggregate) throws IllegalAccessException {
        if (aggregate.getId() == null) {
            log.info("DbContext#detectChanges aggregate id is null");
            return EntityDiff.EMPTY;
        }
        T snapshot = aggregateMap.get(aggregate.getId());
        if (snapshot == null) {
            attach(aggregate);
            log.info("DbContext#detectChanges snapshot is null");
            return EntityDiff.EMPTY;
        }
        return DiffUtils.diff(snapshot, aggregate);
    }

    public T find(ID id) {
        return aggregateMap.get(id);
    }

    public void merge(T aggregate) {
        if (aggregate.getId() != null) {
            T snapshot = null;
            try {
                snapshot = SnapshotUtils.snapshot(aggregate);
            } catch (IOException | ClassNotFoundException e) {
                log.error("DbContext merge failure, error: ", e);
            }
            aggregateMap.put(aggregate.getId(), snapshot);
        }
    }

    public void setId(T aggregate, ID id) {
        ReflectionUtils.writeField("id", aggregate, id);
    }
}
